import Tone from "../core/Tone";
import "../component/CrossFade";
import "../component/Merge";
import "../component/Split";
import "../signal/Signal";
import "../signal/AudioToGain";
import "../signal/Zero";
import "../core/AudioNode";

/**
 *  @class  A spatialized panner node which supports equalpower or HRTF panning.
 *          Tries to normalize the API across various browsers. See Tone.Listener
 *
 *  @constructor
 *  @extends {Tone.AudioNode}
 *  @param {Number} positionX The initial x position.
 *  @param {Number} positionY The initial y position.
 *  @param {Number} positionZ The initial z position.
 */
Tone.Panner3D = function(){

	var options = Tone.defaults(arguments, ["positionX", "positionY", "positionZ"], Tone.Panner3D);
	Tone.AudioNode.call(this);

	/**
	 *  The panner node
	 *  @type {PannerNode}
	 *  @private
	 */
	this._panner = this.input = this.output = this.context.createPanner();
	//set some values
	this._panner.panningModel = options.panningModel;
	this._panner.maxDistance = options.maxDistance;
	this._panner.distanceModel = options.distanceModel;
	this._panner.coneOuterGain = options.coneOuterGain;
	this._panner.coneOuterAngle = options.coneOuterAngle;
	this._panner.coneInnerAngle = options.coneInnerAngle;
	this._panner.refDistance = options.refDistance;
	this._panner.rolloffFactor = options.rolloffFactor;

	/**
	 *  Holds the current orientation
	 *  @type  {Array}
	 *  @private
	 */
	this._orientation = [options.orientationX, options.orientationY, options.orientationZ];

	/**
	 *  Holds the current position
	 *  @type  {Array}
	 *  @private
	 */
	this._position = [options.positionX, options.positionY, options.positionZ];

	// set the default position/orientation
	this.orientationX = options.orientationX;
	this.orientationY = options.orientationY;
	this.orientationZ = options.orientationZ;
	this.positionX = options.positionX;
	this.positionY = options.positionY;
	this.positionZ = options.positionZ;
};

Tone.extend(Tone.Panner3D, Tone.AudioNode);

/**
 *  Defaults according to the specification
 *  @static
 *  @const
 *  @type {Object}
 */
Tone.Panner3D.defaults = {
	"positionX" : 0,
	"positionY" : 0,
	"positionZ" : 0,
	"orientationX" : 0,
	"orientationY" : 0,
	"orientationZ" : 0,
	"panningModel" : "equalpower",
	"maxDistance" : 10000,
	"distanceModel" : "inverse",
	"coneOuterGain" : 0,
	"coneOuterAngle" : 360,
	"coneInnerAngle" : 360,
	"refDistance" : 1,
	"rolloffFactor" : 1
};

/**
 * The ramp time which is applied to the setTargetAtTime
 * @type {Number}
 * @private
 */
Tone.Panner3D.prototype._rampTimeConstant = 0.01;

/**
 *  Sets the position of the source in 3d space.
 *  @param  {Number}  x
 *  @param  {Number}  y
 *  @param  {Number}  z
 *  @return {Tone.Panner3D} this
 */
Tone.Panner3D.prototype.setPosition = function(x, y, z){
	if (this._panner.positionX){
		var now = this.now();
		this._panner.positionX.setTargetAtTime(x, now, this._rampTimeConstant);
		this._panner.positionY.setTargetAtTime(y, now, this._rampTimeConstant);
		this._panner.positionZ.setTargetAtTime(z, now, this._rampTimeConstant);
	} else {
		this._panner.setPosition(x, y, z);
	}
	this._position = Array.prototype.slice.call(arguments);
	return this;
};

/**
 *  Sets the orientation of the source in 3d space.
 *  @param  {Number}  x
 *  @param  {Number}  y
 *  @param  {Number}  z
 *  @return {Tone.Panner3D} this
 */
Tone.Panner3D.prototype.setOrientation = function(x, y, z){
	if (this._panner.orientationX){
		var now = this.now();
		this._panner.orientationX.setTargetAtTime(x, now, this._rampTimeConstant);
		this._panner.orientationY.setTargetAtTime(y, now, this._rampTimeConstant);
		this._panner.orientationZ.setTargetAtTime(z, now, this._rampTimeConstant);
	} else {
		this._panner.setOrientation(x, y, z);
	}
	this._orientation = Array.prototype.slice.call(arguments);
	return this;
};

/**
 *  The x position of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name positionX
 */
Object.defineProperty(Tone.Panner3D.prototype, "positionX", {
	set : function(pos){
		this._position[0] = pos;
		this.setPosition.apply(this, this._position);
	},
	get : function(){
		return this._position[0];
	}
});

/**
 *  The y position of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name positionY
 */
Object.defineProperty(Tone.Panner3D.prototype, "positionY", {
	set : function(pos){
		this._position[1] = pos;
		this.setPosition.apply(this, this._position);
	},
	get : function(){
		return this._position[1];
	}
});

/**
 *  The z position of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name positionZ
 */
Object.defineProperty(Tone.Panner3D.prototype, "positionZ", {
	set : function(pos){
		this._position[2] = pos;
		this.setPosition.apply(this, this._position);
	},
	get : function(){
		return this._position[2];
	}
});

/**
 *  The x orientation of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name orientationX
 */
Object.defineProperty(Tone.Panner3D.prototype, "orientationX", {
	set : function(pos){
		this._orientation[0] = pos;
		this.setOrientation.apply(this, this._orientation);
	},
	get : function(){
		return this._orientation[0];
	}
});

/**
 *  The y orientation of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name orientationY
 */
Object.defineProperty(Tone.Panner3D.prototype, "orientationY", {
	set : function(pos){
		this._orientation[1] = pos;
		this.setOrientation.apply(this, this._orientation);
	},
	get : function(){
		return this._orientation[1];
	}
});

/**
 *  The z orientation of the panner object.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name orientationZ
 */
Object.defineProperty(Tone.Panner3D.prototype, "orientationZ", {
	set : function(pos){
		this._orientation[2] = pos;
		this.setOrientation.apply(this, this._orientation);
	},
	get : function(){
		return this._orientation[2];
	}
});

/**
 *  Proxy a property on the panner to an exposed public propery
 *  @param  {String}  prop
 *  @private
 */
Tone.Panner3D._aliasProperty = function(prop){
	Object.defineProperty(Tone.Panner3D.prototype, prop, {
		set : function(val){
			this._panner[prop] = val;
		},
		get : function(){
			return this._panner[prop];
		}
	});
};

/**
 *  The panning model. Either "equalpower" or "HRTF".
 *  @type {String}
 *  @memberOf Tone.Panner3D#
 *  @name panningModel
 */
Tone.Panner3D._aliasProperty("panningModel");

/**
 *  A reference distance for reducing volume as source move further from the listener
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name refDistance
 */
Tone.Panner3D._aliasProperty("refDistance");

/**
 *  Describes how quickly the volume is reduced as source moves away from listener.
 *  @type {Number}
 *  @memberOf Tone.Panner3D#
 *  @name rolloffFactor
 */
Tone.Panner3D._aliasProperty("rolloffFactor");

/**
 *  The distance model used by,  "linear", "inverse", or "exponential".
 *  @type {String}
 *  @memberOf Tone.Panner3D#
 *  @name distanceModel
 */
Tone.Panner3D._aliasProperty("distanceModel");

/**
 *  The angle, in degrees, inside of which there will be no volume reduction
 *  @type {Degrees}
 *  @memberOf Tone.Panner3D#
 *  @name coneInnerAngle
 */
Tone.Panner3D._aliasProperty("coneInnerAngle");

/**
 *  The angle, in degrees, outside of which the volume will be reduced
 *  to a constant value of coneOuterGain
 *  @type {Degrees}
 *  @memberOf Tone.Panner3D#
 *  @name coneOuterAngle
 */
Tone.Panner3D._aliasProperty("coneOuterAngle");

/**
 *  The gain outside of the coneOuterAngle
 *  @type {Gain}
 *  @memberOf Tone.Panner3D#
 *  @name coneOuterGain
 */
Tone.Panner3D._aliasProperty("coneOuterGain");

/**
 *  The maximum distance between source and listener,
 *  after which the volume will not be reduced any further.
 *  @type {Positive}
 *  @memberOf Tone.Panner3D#
 *  @name maxDistance
 */
Tone.Panner3D._aliasProperty("maxDistance");

/**
 *  Clean up.
 *  @returns {Tone.Panner3D} this
 */
Tone.Panner3D.prototype.dispose = function(){
	Tone.AudioNode.prototype.dispose.call(this);
	this._panner.disconnect();
	this._panner = null;
	this._orientation = null;
	this._position = null;
	return this;
};

export default Tone.Panner3D;

